package top.amot.forceview.force

import top.amot.forceview.layout.I_Layout
import top.amot.forceview.layout.QuadTree_Old
import top.amot.forceview.layout.TreeNode
import top.amot.forceview.layout.VisitCallback
import top.amot.forceview.model.AbstractNode
import kotlin.math.abs
import kotlin.math.sqrt

/**
 * A positive strength causes nodes to attract each other, similar to gravity, while a negative
 * value causes nodes to repel each other, similar to electrostatic charge.
 * 斥力的优化求解
节点间的斥力优化关键为电荷斥力
[4]
求解优化，最基本的一个节点所受的力，需要与其他所有节点进行计算求和，复杂度为 。
整合受力
而四叉树结构与 Barnes-Hut
[5]
近似，复杂度可降为 。当前节点所受周围点的斥力进行整合处理，大小由 Barnes-Hut 近似精度 (默认值为) 决定，最后根据 Velocity Verlet
[6]
对速度进行求解。
象限面积 ，节点 (node) 与象限点 (quad) 形成的面积
 */


class ForceManyBody : AbstractForce() {
    private var distanceMin2 = 1.0f
    private var distanceMax2 = Float.POSITIVE_INFINITY
    private lateinit var strengths: FloatArray
    private var strengthCalculation: NodeCalculation? = null
    private var theta2 = DEFAULT_THETA2
    private var alpha = 0.0f
    private var node: AbstractNode? = null
    override fun initialize(layout: I_Layout) {
        super.initialize(layout)
        initialize()
    }

    override fun apply(alpha: Float) {
        this.alpha = alpha
        val tree = QuadTree_Old.create(nodes, { node -> x(node) }) { node -> y(node) }
            .visitAfter { node: TreeNode, x0: Float, y0: Float, x1: Float, y1: Float ->
                accumulate(
                    node,
                    x0,
                    y0,
                    x1,
                    y1
                )
            }
        for (node in nodes) {
            this.node = node
            tree.visit { node: TreeNode, x0: Float, y0: Float, x1: Float, y1: Float ->
                applyVisit(
                    node,
                    x0,
                    y0,
                    x1,
                    y1
                )
            }
        }

    }

    fun strength(c: NodeCalculation?): ForceManyBody {
        strengthCalculation = c
        return this
    }

    fun theta(theta: Float): ForceManyBody {
        theta2 = theta * theta
        return this
    }

    fun distanceMin(distanceMin: Float): ForceManyBody {
        distanceMin2 = distanceMin * distanceMin
        return this
    }

    fun distanceMax(distanceMax: Float): ForceManyBody {
        distanceMax2 = distanceMax * distanceMax
        return this
    }

    private fun initialize() {
        strengths = FloatArray(nodes.size)

        nodes.forEachIndexed { i, node ->
            strengths[node.index] = strength(i)
        }

    }

    private fun strength(i: Int): Float {
        return strengthCalculation?.calculateNode(nodes[i])?: DEFAULT_STRENGTH

    }

    private fun accumulate(
        node: TreeNode,
        x0: Float,
        y0: Float,
        x1: Float,
        y1: Float
    ): Boolean {
        var strength = 0.0f
        var weight = 0.0f
        var x = 0.0f
        var y = 0.0f
        var no: TreeNode?
        if (!node.isLeaf) {
            for (i in 0..3) {
                var c=0.0f
                no = node.quadrants[i]
                if (no  != null && abs(no.strength).also { c = it } > 0) {
                    strength += no.strength
                    weight += c
                    x += c * no.x
                    y += c * no.y
                }
            }
            node.x = x / weight
            node.y = y / weight
        } else {
            node.x = node.data!!.x
            node.y = node.data!!.y
            var nodeNext : TreeNode? = node
            do {
                strength += strengths[nodeNext!!.data!!.index]
            } while (nodeNext?.next.also { nodeNext = it } != null)
        }
        node.strength = strength
        return false
    }

    private fun applyVisit(
        node: TreeNode,
        x0: Float,
        y0: Float,
        x1: Float,
        y1: Float
    ): Boolean {
        if (node.strength == 0.0f) {
            return true
        }
        var x = node.x - this.node!!.x
        var y = node.y - this.node!!.y
        var w = x1 - x0
        var l = x * x + y * y
        // Barnes-Hut成立
        // 如何点非常近，冲突的时候随机方向
        if (w * w / theta2 < l) {
            if (l < distanceMax2) {
                if (x == 0.0f) {
                    x = jiggle()
                    l += x * x
                }
                if (y == 0.0f) {
                    y = jiggle()
                    l += y * y
                }
                if (l < distanceMin2) {
                    l = sqrt(distanceMin2 * l)
                }
                this.node!!.vx += x * node.strength * alpha / l
                this.node!!.vy += y * node.strength * alpha / l
            }
            return true
            // Barnes-Hut不成立且 quad 有节点
        } else if (!node.isLeaf || l >= distanceMax2) {
            return false
        }
        // 排除自身对自身影响，还可以继续向下遍历
        if (node.data !== this.node || node.next != null) {
            if (x == 0.0f) {
                x = jiggle()
                l += x * x
            }
            if (y == 0.0f) {
                y = jiggle()
                l += y * y
            }
            if (l < distanceMin2) {
                l = sqrt(distanceMin2 * l)
            }
        }
        var node1: TreeNode? =node
        do {
            if (node1!!.data !== this.node) {
                w = strengths[node1!!.data!!.index] * alpha / l
                this.node!!.vx += x * w
                this.node!!.vy += y * w
            }
            node1 = node1?.next
        } while ( node1  != null)
        return false
    }

    companion object {
        const val NAME = "Charge"
        private const val DEFAULT_STRENGTH = -30.0f
        private const val DEFAULT_THETA2 = 0.81f

        private fun x(node: AbstractNode): Float {
            return node.x
        }

        private fun y(node: AbstractNode): Float {
            return node.y
        }
    }
}